package com.paziy.notes.core;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

/**
 * Main class of music compositions
 *
 * @author thecederick@gmail.com (Paziy Evgeniy)
 * @version 0.2
 * @since 0.1 (27.11.13 11:38)
 */
public class Composition {

    /**
     * Information about composition
     * @see com.paziy.notes.core.CompositionInformation
     */
    private CompositionInformation information;

    /**
     * Time signature of composition
     * @see com.paziy.notes.core.TimeSignature
     */
    private TimeSignature timeSignature;

    /**
     * Temp of composition
     * @see com.paziy.notes.core.Temp
     */
    private Temp temp;

    /**
     * List of track groups
     * @see com.paziy.notes.core.TrackGroup
     */
    private List<TrackGroup> trackGroups;

    /**
     * Default constructor.
     * All fields initializing by their class default constructor.
     */
    public Composition() {
        information = new CompositionInformation();
        timeSignature = new TimeSignature();
        temp = new Temp();
        trackGroups = new LinkedList<TrackGroup>();
    }

    /**
     * Creates composition with determined time signature and temp
     * @see com.paziy.notes.core.TimeSignature
     * @see com.paziy.notes.core.Temp
     *
     * @param timeSignature value of time signature. For allowed values see:
     *                      @see com.paziy.notes.core.Composition#checkTimeSignature(TimeSignature)
     * @param temp value of temp. For allowed values see:
     *             @see com.paziy.notes.core.Composition#checkTemp(Temp)
     */
    public Composition(TimeSignature timeSignature, Temp temp) {
        checkTimeSignature(timeSignature);
        this.timeSignature = timeSignature;

        checkTemp(temp);
        this.temp = temp;

        trackGroups = new LinkedList<TrackGroup>();
    }

    /**
     * Creates composition with determined time signature, temp and composition information
     * @see com.paziy.notes.core.CompositionInformation
     * @see com.paziy.notes.core.TimeSignature
     * @see com.paziy.notes.core.Temp
     *
     * @param information about author and composition. For allowed values see:
     *                    @see com.paziy.notes.core.Composition#checkInformation(CompositionInformation)
     * @param timeSignature value of time signature. For allowed values see:
     *                      @see com.paziy.notes.core.Composition#checkTimeSignature(TimeSignature)
     * @param temp value of temp. For allowed values see:
     *             @see com.paziy.notes.core.Composition#checkTemp(Temp)
     */
    public Composition(CompositionInformation information, TimeSignature timeSignature, Temp temp) {
        this(timeSignature, temp);

        checkInformation(information);
        this.information = information;
    }

    /**
     * Creates composition with determined time signature, temp and track groups collection
     * @see com.paziy.notes.core.TimeSignature
     * @see com.paziy.notes.core.Temp
     * @see com.paziy.notes.core.TrackGroup
     *
     * @param timeSignature value of time signature. For allowed values see:
     *                      @see com.paziy.notes.core.Composition#checkInformation(CompositionInformation)
     * @param temp value of temp. For allowed values see:
     *             @see com.paziy.notes.core.Composition#checkTemp(Temp)
     * @param trackGroups collection of track groups
     */
    public Composition(TimeSignature timeSignature, Temp temp, Collection<TrackGroup> trackGroups) {
        checkTimeSignature(timeSignature);
        this.timeSignature = timeSignature;

        checkTemp(temp);
        this.temp = temp;

        this.trackGroups = new LinkedList<TrackGroup>(trackGroups);
    }

    /**
     * Creates composition with determined time signature, temp, track groups collection
     * and information about composition and author
     * @see com.paziy.notes.core.CompositionInformation
     * @see com.paziy.notes.core.TimeSignature
     * @see com.paziy.notes.core.Temp
     * @see com.paziy.notes.core.TrackGroup
     *
     * @param information about author and composition. For allowed values see:
     *                    {@see com.paziy.notes.core.Composition#checkInformation(CompositionInformation)}
     * @param timeSignature value of time signature. For allowed values see:
     *                      @see com.paziy.notes.core.Composition#checkTimeSignature(TimeSignature)
     * @param temp value of temp. For allowed values see:
     *             @see com.paziy.notes.core.Composition#checkTemp(Temp)
     * @param trackGroups collection of track groups
     */
    public Composition(CompositionInformation information, TimeSignature timeSignature,
                       Temp temp, Collection<TrackGroup> trackGroups) {
        this(timeSignature, temp, trackGroups);

        checkInformation(information);
        this.information = information;
    }

    /**
     * Tests information about composition for not allowed values.
     * Not allowed value is null all else values are allowed
     *
     * @param information instance {CompositionInformation} to test
     * @throws RuntimeException
     */
    private static void checkInformation(CompositionInformation information) {
        if (information == null)
            throw new RuntimeException(new NullPointerException("Composition: information can't be null"));
    }

    /**
     * Tests time signature for not allowed values.
     * Not allowed value is null all else values are allowed
     *
     * @param timeSignature instance of {TimeSignature} to test
     * @throws RuntimeException
     */
    private static void checkTimeSignature(TimeSignature timeSignature) {
        if (timeSignature == null)
            throw new RuntimeException(new NullPointerException("Composition: information can't be null"));
    }

    /**
    * Tests temp for not allowed values.
    * Not allowed value is null all else values are allowed
    *
    * @param temp instance of {Temp} to test
    * @throws RuntimeException
    */
    private static void checkTemp(Temp temp) {
        if (temp == null)
            throw new RuntimeException(new NullPointerException("Composition: information can't be null"));
    }

    /**
     * Getter for field information
     *
     * @return information about current composition
     */
    public CompositionInformation getInformation() {
        return information;
    }

    /**
     * Getter for field timeSignature
     *
     * @return time signature of current composition
     */
    public TimeSignature getTimeSignature() {
        return timeSignature;
    }

    /**
     * Getter for field temp
     *
     * @return temp of current composition
     */
    public Temp getTemp() {
        return temp;
    }

    /**
     * Returns the number of {TrackGroup} in this composition.
     *
     * @return count of track groups of composition
     */
    public int getCountTrackGroups() {
        return trackGroups.size();
    }

    /**
     * Returns the {TrackGroup} at the specified position in this composition.
     *
     * @param index index of the element to return
     * @return the {TrackGroup} at the specified position in this composition.
     */
    public TrackGroup getTrackGroup(int index) {
        return trackGroups.get(index);
    }

    /**
     * Appends the specified {TrackGroup} to the end of this composition (optional operation).
     *
     * @param newTrackGroup {TrackGroup} to be appended to this composition
     * @return true (as specified by Collection.add(E))
     */
    public boolean addTrackGroup(TrackGroup newTrackGroup) {
        return trackGroups.add(newTrackGroup);
    }

    /**
     * Inserts the specified {TrackGroup} at the specified position in this composition.
     * Shifts the {TrackGroup} currently at that position (if any) and any subsequent {TrackGroup} to the right
     * (adds one to their indices).
     *
     * @param index index at which the specified {TrackGroup} is to be inserted
     * @param newTrackGroup {TrackGroup} to be inserted
     */
    public void addTrackGroup(int index, TrackGroup newTrackGroup) {
        trackGroups.add(index, newTrackGroup);
    }

    /**
     * Removes the {TrackGroup} at the specified position in this composition (optional operation).
     * Shifts any subsequent {TrackGroup} to the left (subtracts one from their indices).
     * Returns the {TrackGroup} that was removed from the composition.
     *
     * @param index the index of the {TrackGroup} to be removed
     * @return the {TrackGroup} previously at the specified position
     */
    public TrackGroup removeTrackGroup(int index) {
        return trackGroups.remove(index);
    }

    /**
     * Removes the first occurrence of the specified {TrackGroup} from this composition, if it is
     * present. If this composition does not contain the {TrackGroup}, it is unchanged. More formally,
     * removes the {TrackGroup} with the lowest index i such that (o==null ? get(i)==null : o.equals(get(i))).
     * Returns true if this composition contained the specified {TrackGroup}
     * (or equivalently, if this list changed as a result of the call).
     *
     * @param trackGroup {TrackGroup} to be removed from this composition, if present
     * @return true if this composition contained the specified {TrackGroup}
     */
    public boolean removeTrackGroup(TrackGroup trackGroup) {
        return trackGroups.remove(trackGroup);
    }
}
